package ru.ipo.dces.client;

import com.jgoodies.looks.plastic.Plastic3DLookAndFeel;
import ru.ipo.dces.PluginsRegistry;
import ru.ipo.dces.client.components.StatusBar;
import ru.ipo.dces.client.messages.PopupMessageType;
import ru.ipo.dces.clientservercommunication.*;
import ru.ipo.dces.exceptions.GeneralRequestFailureException;
import ru.ipo.dces.exceptions.ServerReturnedError;
import ru.ipo.dces.pluginapi.Plugin;
import ru.ipo.dces.pluginapi.PluginEnvironment;
import ru.ipo.dces.plugins.admin.LogoutPluginV2;
import ru.ipo.dces.plugins.admin.ResultsPlugin;
import ru.ipo.dces.plugins.admin.UserHistoryPlugin;
import ru.ipo.dces.server.ServerFacade;
import ru.ipo.dces.server.ServerFacadeAsynchronous;
import ru.ipo.dces.server.ServerFailReason;
import ru.ipo.dces.server.http.HttpServer;
import ru.ipo.dces.utils.FileSystemUtils;
import ru.ipo.dces.utils.ResponseHandler;
import ru.ipo.problemsapi.ExtendedProblem;
import ru.ipo.problemsapi.Problem;
import ru.ipo.problemsapi.ZipProblem;

import javax.swing.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.Proxy;
import java.util.HashMap;
import java.util.HashSet;

public class Controller {

    private static ContestConnection contestConnection;
    private static ServerFacadeAsynchronous asynchronousServer;
    private static ServerFacade server;
    private static ClientDialog clientDialog;

    //TODO move the field to an appropriate place
    private static HashMap<Integer, ExtendedProblem> problems = null;

    private static PluginsRegistry pluginsRegistry;

    /**
     * Добавление Plugin'а в соревнование
     *
     * @param problemID problem id
     * @param problem   the problem for which the plugin is added
     */
    private static void addPlugin(int problemID, Problem problem) {
        ExtendedProblem dp = new ExtendedProblem(problem);
        PluginEnvironmentImpl pe = new PluginEnvironmentImpl(problemID, dp);

        Plugin p;
        try {
            Class<? extends Plugin> mainClass = pluginsRegistry.getPlugin(Plugin.class, dp.getClientPlugin());
            //NullPointerException is possible in the next line
            Constructor<? extends Plugin> constructor = mainClass.getConstructor(PluginEnvironment.class);
            p = constructor.newInstance((PluginEnvironment) pe);
            pe.init(p);
        } catch (Exception e) {
            //something wrong with plugins
//            Controller.getLogger().log("Не удалось загрузить плагин", LogMessageType.Error, Localization.LOGGER_NAME);
            showPopupMessage("Не удалось загрузить плагин", PopupMessageType.GeneralServerError);
        }
    }

    /**
     * Добавиление административного Plugin'а
     *
     * @param pluginClass class of new Plugin
     * @return added plugin
     */
    public static Plugin addAdminPlugin(Class<? extends Plugin> pluginClass) {
        try {
            PluginEnvironmentImpl pe = new PluginEnvironmentImpl(Localization.getAdminPluginName(pluginClass));
            final Constructor<? extends Plugin> constructor = pluginClass.getConstructor(PluginEnvironment.class);
            Plugin p = constructor.newInstance(pe);
            pe.init(p);
            return p;
        } catch (Exception e) {
            System.out.println("Admin plugin " + pluginClass.getSimpleName() + " has no constuctor(PluginEnvironment)");
            e.printStackTrace();
            System.exit(1); //it should not occur
            return null;
        }
    }

    //TODO заменить слово клиентский плагин на слово "модуль решения задач", серверный плагин на "модуль проверки решений"
    public static void refreshParticipantInfo(boolean refreshProblems, final boolean refreshPlugins) {
        ResponseHandler<GetContestDataResponse> contestDataResponseHandler = new ResponseHandler<GetContestDataResponse>() {
            @Override
            public void success(GetContestDataResponse rs) {
                ProblemDescription[] problemDescriptions = rs.problems;

                problems = new HashMap<Integer, ExtendedProblem>();

                for (int i = 0, len = problemDescriptions.length; i < len; i++) {

                    ProblemDescription pd = problemDescriptions[i];
                    File problemFile = getProblemFileByID(pd.id);
                    if (!FileSystemUtils.saveBytesToFile(pd.problem, problemFile))
                        showPopupMessage("Не удалось обновить задачу с идентификатором: " + pd.id,
                                PopupMessageType.GeneralServerError);

                    Problem p;
                    try {
                        p = new ZipProblem(problemFile);
                    } catch (IOException e) {
                        showPopupMessage("Не удалось загрузить плагины", PopupMessageType.GeneralServerError);
                        return;
                    }

                    addPlugin(pd.id, p);

                    problems.put(pd.id, new ExtendedProblem(p));
                }

                if (refreshPlugins) {
                    //get contest plugins
                    HashSet<String> contestPlugins = new HashSet<String>();

                    for (ExtendedProblem extendedProblem : problems.values())
                        contestPlugins.add(extendedProblem.getClientPlugin());

                    //remove contest plugins
                    for (String contestPlugin : contestPlugins) {
                        File pluginFile = new File(Settings.getInstance().getPluginsDirectory() + '/' + contestPlugin + ".jar");
                        if (!pluginFile.delete())
                            showPopupMessage("Не удалось обновить плагин: " + pluginFile.getName(),
                                    PopupMessageType.GeneralServerError);
                    }
                    pluginsRegistry.clear();
                }

                addAdminPlugin(UserHistoryPlugin.class);
                addAdminPlugin(ResultsPlugin.class);
                addAdminPlugin(LogoutPluginV2.class);
            }

            @Override
            public void fail(ServerFailReason exception) {
            }
        };

        GetContestDataRequest rq = new GetContestDataRequest();
        rq.contestID = -1;
        rq.infoType = GetContestDataRequest.InformationType.ParticipantInfo;
        rq.extendedData = null; //TODO download only problems that are not already on disk
        rq.sessionID = contestConnection.getSessionID();

        String statusBarMessageText = "";
        if (refreshPlugins)
            statusBarMessageText += "плагинов";
        if (refreshProblems) {
            if (refreshPlugins)
                statusBarMessageText += " и ";
            statusBarMessageText += "условий задач";
        }

        doRequest(rq, contestDataResponseHandler, "Обновление " + statusBarMessageText);
    }

    /**
     * ������ �������
     *
     * @param args the command line input
     */
    public static void main(String[] args) {
        //PlasticLookAndFeel.setPlasticTheme(new DesertYellow());
        try {
            //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            UIManager.setLookAndFeel(new Plastic3DLookAndFeel());
        } catch (Exception e) {
            System.out.println("Failed to set system look and feel");
            System.exit(1);
        }
        final Proxy proxy = Settings.getInstance().getProxy();
        final String host = Settings.getInstance().getHost();
        if (proxy == null)
            server = new HttpServer(host);
        else
            server = new HttpServer(host, proxy);
        asynchronousServer = new ServerFacadeAsynchronous(server);
        processArgs(args);

        pluginsRegistry = new PluginsRegistry(
                new File(Settings.getInstance().getPluginsDirectory()),
                server
        );

        clientDialog = new ClientDialog();
        clientDialog.initGUI();
        clientDialog.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        clientDialog.setVisible(true);
    }

    private static void processArgs(String[] args) {
        if (args.length == 0) return;

        if (args[0].equals("-help")) {
            System.out.println("Usage: -help shows this help");
            System.out.println("       -createdb <superuse\r-login> <superuser-password>");
            System.exit(0);
        } else if (args[0].equals("-createdb")) {
            if (args.length != 3)
                System.out.println("need exactly two additional paramenters: superuser login and superuser password");
            else {
                final CreateDataBaseRequest createDataBaseRequest = new CreateDataBaseRequest();
                createDataBaseRequest.login = args[1];
                createDataBaseRequest.password = args[2];
                try {
                    server.doRequest(createDataBaseRequest);
                    System.exit(0);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                    System.exit(1);
                }
            }
        }
    }

    public static void getAvailablePlugins(ResponseHandler<AvailablePluginsResponse> handler, PluginSide pluginSide) {
        AvailablePluginsRequest request = new AvailablePluginsRequest();
        request.sessionID = contestConnection.getSessionID();
        request.pluginSide = pluginSide;
        doRequest(request, handler, "Получение списка доступных модулей");
    }

    public static void getAvailableClientPlugins(ResponseHandler<AvailablePluginsResponse> handler) {
        getAvailablePlugins(handler, PluginSide.Client);
    }

    public static void getAvailableServerPlugins(final ResponseHandler<AvailablePluginsResponse> handler) {
        getAvailablePlugins(handler, PluginSide.Server);
    }

    public static void downloadPlugin(String pluginAlias, final ResponseHandler<DownloadPluginResponse> handler) {
        DownloadPluginRequest request = new DownloadPluginRequest();
        request.pluginAlias = pluginAlias;
        request.sessionID = contestConnection.getSessionID();
        request.side = PluginSide.Client;
        doRequest(request, handler, "Загрузка модуля решения задачи");
    }

    public static void addContest(ContestDescription cd, ResponseHandler<CreateContestResponse> handler) {
        CreateContestRequest contestRequest = new CreateContestRequest();
        contestRequest.sessionID = contestConnection.getSessionID();
        contestRequest.contest = cd;
        doRequest(contestRequest, handler, "Добавление соревнования");
    }

    public static void getContestData(int contestID, final ResponseHandler<GetContestDataResponse> handler) {
        GetContestDataRequest gcdr = new GetContestDataRequest();
        gcdr.contestID = contestID;
        gcdr.extendedData = null;
        gcdr.infoType = GetContestDataRequest.InformationType.NoInfo;
        gcdr.sessionID = contestConnection.getSessionID();
        doRequest(gcdr, handler, "Получение данных соревнования");
    }

    public static void adjustContestData(AdjustContestRequest acr) {
        acr.sessionID = contestConnection.getSessionID();
        doRequest(acr, null, "Обновление данных соревнования");
    }

    public static void registerAnonymouslyToContest(String login, char[] password, int contestID, String[] userData,
                                                    final ResponseHandler<AcceptedResponse> handler) {
        RegisterToContestRequest r = new RegisterToContestRequest();
        r.sessionID = null;
        r.contestID = contestID;
        r.user = new UserDescription();
        r.user.dataValue = userData;
        r.user.login = login;
        r.user.password = new String(password);
        r.user.userType = UserDescription.UserType.Participant;

        doRequest(r, handler, "Регистрация для участия в соревновании");
    }

    public static File getProblemFileByID(int problemID) {
        return new File(Settings.getInstance().getProblemsDirectory() + '/' + problemID + ".problem");
    }

    public static File getProblemDebugDirectoryByID(int problemID) {
        final File debugDir = new File(Settings.getInstance().getProblemsDirectory() + "/debug");
        debugDir.mkdir();
        return new File(debugDir.getPath() + '/' + problemID);
    }

    public static ClientDialog getClientDialog() {
        return clientDialog;
    }

    public static void getUsers(int contestID, final ResponseHandler<GetUsersResponse> handler) {
        final GetUsersRequest req = new GetUsersRequest();
        req.contestID = contestID;
        req.sessionID = contestConnection.getSessionID();

        doRequest(req, handler, "Получение списка пользователей");
    }

    public static void adjustPlugin(String alias, String description, File file, PluginSide pluginSide) {
        AdjustPluginRequest r = new AdjustPluginRequest();

        r.side = pluginSide;
        r.pluginAlias = alias;

        if (description.equals(""))
            r.description = null;
        else
            r.description = description;

        r.sessionID = contestConnection.getSessionID();

        //load plugin data from file
        if (file != null) {
            r.pluginData = new byte[(int) file.length()];
            try {
                InputStream is = new FileInputStream(file);
                if (is.read(r.pluginData) < r.pluginData.length) throw new IOException();
            } catch (IOException ignore) {
            }
        } else
            r.pluginData = null;

        doRequest(r, null, "Обновление модуля");
    }

    public static void removePlugin(String alias, PluginSide pluginSide) {
        RemovePluginRequest r = new RemovePluginRequest();
        r.side = pluginSide;
        r.pluginAlias = alias;
        r.sessionID = contestConnection.getSessionID();
        doRequest(r, null, "Удаление модуля");
    }

    public static void removeClientPlugin(String alias) {
        removePlugin(alias, PluginSide.Client);
    }

    public static void removeServerPlugin(String alias) {
        removePlugin(alias, PluginSide.Server);
    }

    public static void addUser(final String login, char[] password, String[] dataValue, UserDescription.UserType ut,
                               int contestID, final ResponseHandler<AcceptedResponse> handler) {
        RegisterToContestRequest cur = new RegisterToContestRequest();

        UserDescription ud = new UserDescription();

        ud.login = login;
        ud.password = new String(password);
        ud.dataValue = dataValue;
        ud.userType = ut;

        cur.contestID = contestID;
        cur.sessionID = contestConnection.getSessionID();
        cur.user = ud;

        doRequest(cur, handler, "Добавление пользователя");
    }

    public static void removeUser(int userID, final ResponseHandler<AcceptedResponse> handler) {
        RemoveUserRequest rur = new RemoveUserRequest();
        rur.sessionID = contestConnection.getSessionID();
        rur.userID = userID;
        doRequest(rur, handler, "Удаление пользователя");
    }

    public static ContestConnection getContestConnection() {
        return contestConnection;
    }

    public static void connectToContest(ContestDescription contest, String login, char[] password) throws ServerReturnedError, GeneralRequestFailureException {
        contestConnection = new ContestConnection(server, contest, login, password);
    }

    public static void logout() {
        problems = null;
        contestConnection.disconnect();
        contestConnection = null;
        clientDialog.initialState();
    }

    public static boolean isContestUnknownMode() {
        return contestConnection == null || contestConnection.getUser().userType == UserDescription.UserType.SuperAdmin;
    }

    public static boolean isSuperAdmin() {
        return contestConnection != null && contestConnection.getUser().userType == UserDescription.UserType.SuperAdmin;
    }

    public static boolean isAdmin() {
        return contestConnection != null && contestConnection.getUser().userType == UserDescription.UserType.ContestAdmin;
    }

    public static boolean isParticipant() {
        return contestConnection != null && contestConnection.getUser().userType == UserDescription.UserType.Participant;
    }

    public static boolean isNoConnection() {
        return contestConnection == null;
    }

    public static ExtendedProblem getProblemById(int id) {
        if (problems == null)
            return null;
        else
            return problems.get(id);
    }

    public static <Resp extends Response> void doRequest(Request request, final ResponseHandler<Resp> handler, String message) {
        final StatusBar.Message statusBarMessage = clientDialog.getStatusBar().addMessage(message);

        ResponseHandler<Resp> handlerWrapper = new ResponseHandler<Resp>() {
            @Override
            public void success(Resp response) {
                clientDialog.getStatusBar().removeMessage(statusBarMessage);

                if (handler != null)
                    handler.success(response);
            }

            @Override
            public void fail(ServerFailReason exception) {

                if (exception.getExceptionType() == ServerFailReason.ServerExceptionType.ServerReturnedError)
                    clientDialog.showPopupMessage(
                            exception.getMessage(),
                            PopupMessageType.ServerReturnedError
                    );

                if (exception.getExceptionType() == ServerFailReason.ServerExceptionType.GeneralRequestFailureException)
                    clientDialog.showPopupMessage(
                            exception.getMessage(),
                            PopupMessageType.GeneralServerError
                    );

                clientDialog.getStatusBar().removeMessage(statusBarMessage);

                if (handler != null)
                    handler.fail(exception);
            }
        };

        asynchronousServer.doRequest(request, handlerWrapper);
    }

    public static void showPopupMessage(String message, PopupMessageType messageType) {
        clientDialog.showPopupMessage(message, messageType);
    }
}
